package com.android.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

import com.android.networking.message.DataMessage;
import com.android.networking.message.DataPacket;
import com.android.networking.connection.*;

/**
 * Round Logic Class - Manages the game's round control logic.
 * 
 * @author David Alban
 * @version 1.0 
 */
public class RoundLogic
{
	/**
	 * Round start time in milliseconds
	 */
	private long startTime;
	/**
	 * Round end time in milliseconds
	 */
	private long endTime;
	/**
	 * Maximum round time allowed in milliseconds
	 */
	public static final long ROUND_TIME = 600000;
	/**
	 * Round threshold allowed in milliseconds
	 */
	public static final long ROUND_THRESHOLD = 30000;
	/**
	 * Round time in milliseconds
	 */
	private long roundTime;
	/**
	 * Round threshold time in milliseconds
	 */
	private long thresholdTime;
	/**
	 * Round threshold start time in milliseconds
	 */
	private long thresholdStartTime;
	/**
	 * Round threshold end time in milliseconds
	 */
	private long thresholdEndTime;
	/**
	 * Player's round score in milliseconds
	 */
	private int roundScore;
	/**
	 * Maximum round score in milliseconds
	 */
	private int maxScore;
	/**
	 * Index of player with the maximum round score
	 */
	private int maxScoreIndex;
	/**
	 * Determines if round is over
	 * false = Round is not over,
	 * true	 = Round is over
	 */
	private boolean isRoundOver;
	/**
	 * Determines if round is complete
	 * false = Round is incomplete,
	 * true	 = Round is complete
	 */
	private boolean isRoundComplete;
	/**
	 * Round's first data message: 
	 * false = any data message after the first,
	 * true	 = first data message
	 */
	private boolean firstDataMessage;
	/**
	 * Error from data message packet: 
	 * false = data message packet is error free,
	 * true	 = data message packet has errors
	 */
	private boolean dataMessageError;
	/**
	 * Data message sequence number
	 */
    private int sourceSeqNumber;
	/**
	 * Data points path length
	 */
    private int pointsPathLen;
	/**
	 * Array list of points, creates a path
	 */
	private ArrayList pointPath;
	/**
	 * String representation of the word to be drawn.
	 */
	private String word;
	/**
	 * Part of speech of word 
	 * 
	 * Adjective		describes a noun
	 * Adverb			describes a verb, adjective or adverb
	 * Conjunction		joins clauses or sentences or words
	 * Interjection		short exclamation, sometimes inserted into a sentence
	 * Noun				thing or person
	 * Preposition		links a noun to another word
	 * Pronoun			replaces a noun
	 * Verb				action or state
	 */ 
	private String wordType;
	/**
	 * Array of hints on the word 
	 */
	private ArrayList hint;
	/**
	 * String representation of the first hint on the word 
	 */
	private String hint1;
	/**
	 * String representation of the second hint on the word 
	 */
	private String hint2;
	/**
	 * String representation of the guess on the word
	 */
	private String guess;
	/**
	 * Player's guess time in milliseconds
	 */
	private long guessTime;
	/**
	 * Number of hints used by guesser
	 */
	private int hintCount;
	/**
	 * Data message
	 */
	private DataMessage dataMessage;
	/**
	 * Data packet
	 */
	private DataPacket localPack;
	/**
	 * Word list
	 */
	private WordList wordList;
	/**
	 * Instance of connection
	 */
	private Connection connection;
	/**
	 * Instance of client information pool
	 */
	private static HashMap<String,ClientInfoPool> clientInfo;
	/**
	 * Collection of clients
	 */
	private Collection<ClientInfoPool> clientCollection;
	/**
	 * Array of clients
	 */
	private ClientInfoPool [] clientArray;
	/**
	 * Instance of player game information
	 */
	private PlayerGameInfo playerGameInfo;
	
	/**
	 * Constructor - Initializes the round logic class.
	 */
	public RoundLogic()
	{
		sourceSeqNumber = 0;
		pointsPathLen   = 0;
		pointPath       = null;
		word            = null;
		wordType        = null;
		hint1           = null;
		hint2           = null;
		guess           = null;
		guessTime       = 0;
		hintCount       = 0;
		
		localPack = new DataPacket();		
		wordList  = new WordList();
	}
	
	/**
	 * Accessor Method - Returns the current round time in milliseconds.
	 * @return	roundTime		Current round time in milliseconds
	 */
	public long getRoundTime()
	{		
		endTime = System.currentTimeMillis();		
		roundTime = endTime - startTime;
		
		return roundTime;
	}
	
	/**
	 * Accessor Method - Returns the round status. Returns false if round is 
	 * not over, and true if the round is over.
	 * @return	isRoundOver		false	Round is not over
	 * 							true	Round is over
	 */
	public boolean isRoundOver()
	{
		return isRoundOver;
	}
	
	/**
	 * Accessor Method - Returns the round status. Returns false if round is 
	 * incomplete, and true if the round is complete.
	 * @return	isRoundComplete	false	Round is incomplete
	 * 							true	Round is complete
	 */
	public boolean isRoundComplete()
	{
		return isRoundComplete;
	}
	
	/**
	 * Modifier Method - Play one round of the game
	 */
	public void playRound()
	{
		startTime = System.currentTimeMillis();
		endTime   = System.currentTimeMillis();
		roundTime          = 0;
		thresholdTime      = 0;
		thresholdStartTime = 0;
		thresholdEndTime   = 0;
		
		roundScore    = 0;
		maxScore      = 0;
		maxScoreIndex = 0;
		
		isRoundOver = false;
		isRoundComplete = false;
		
		firstDataMessage = true;
		dataMessageError = false;
		
		// Update the client array
		clientInfo = Connection.getClientPool();
		clientCollection = clientInfo.values();
		clientArray = (ClientInfoPool [])clientCollection.toArray();
		
		// Send drawer and guesser word set to start round
		sendWordSet();
		
		while(!isRoundOver)
		{	
			if(roundTime < (ROUND_TIME + ROUND_THRESHOLD))
			{
				// Wait 10 seconds
				wait(10);
			
				endTime = System.currentTimeMillis();		
				roundTime = endTime - startTime;
			}
			else
			{
				isRoundOver = true;
			}
		}
		
		// Set score
		setScore();
		
		// Set player mode
		setPlayerMode();
		
		isRoundComplete = true;
	}
	
	/**
	 * Modifier Method - Extracts parameters from data message and performs
	 * functions if necessary.	
	 * @param	DataMessage		Data message
	 * @param	clientIP		Client IP
	 */
	public void parseMessage (DataMessage DataMessage, String clientIP)
	{
		localPack = DataMessage.getLocalPack();
		
		// Extract parameters from data message
		sourceSeqNumber = localPack.getSourceSeqNumber();
		pointsPathLen   = localPack.getPointsPathLen();		
		pointPath       = localPack.getPointPath();	
		word            = localPack.getWord();
		wordType        = localPack.getExtraData();
		hint            = localPack.getHint();
		hint1           = (String)hint.get(0);		
		hint2           = (String)hint.get(1);	
		guess           = localPack.getAnswer();
		guessTime       = localPack.getGuessTime();
		hintCount       = localPack.getHintCount();
		
		// Check for drawing data
		if(pointsPathLen != 0 & pointPath != null)
		{
			for (int i = 0; i < clientArray.length; i++)
			{
				playerGameInfo = clientArray[i].getPlayerGameInfo();
				
				sourceSeqNumber++;
					
				// Check if player is a guesser
				if(playerGameInfo.getPlayerMode())
				{
					dataMessage = new DataMessage(sourceSeqNumber,pointsPathLen,pointPath,null,null,null,0,0,null);
						
					// Send drawing on screen method
				}
			}
		}
		
		if(word != null)
		{
			// Error, server should never receive the word
			dataMessageError = true;
		}
		
		if(wordType != null)
		{
			// Error, server should never receive the word type
			dataMessageError = true;
		}
		
		if(hint1 != null)
		{
			// Error, server should never receive first hint on the word
			dataMessageError = true;
		}
		
		if(hint2 != null)
		{
			// Error, server should never receive second hint on the word
			dataMessageError = true;
		}
		
		if(guess != null)
		{
			// Check if guess is correct
			if(guess.equals(word))
			{
				endTime = System.currentTimeMillis();		
				roundTime = endTime - startTime;
				
				if(roundTime < (ROUND_TIME + ROUND_THRESHOLD))
				{
					// Check if round threshold start time has not been set
					if(thresholdStartTime == 0)
					{
						thresholdStartTime = endTime;
					}
				
					thresholdEndTime = System.currentTimeMillis();
					thresholdTime = thresholdEndTime - thresholdStartTime;
					
					// Check if round threshold has elapsed
					if(thresholdTime < ROUND_THRESHOLD)
					{
						for (int i = 0; i < clientArray.length; i++)
						{
							if(clientIP == clientArray[i].getClientIpAddr());
							{
								playerGameInfo = clientArray[i].getPlayerGameInfo();
								
								playerGameInfo.setGuessTime(guessTime);
								playerGameInfo.setHintsUsed(hintCount);
								
								i = clientArray.length;
							}
						}
					}
					else
					{
						// Round is over
						isRoundOver = true;
					}
				}
				else
				{
					// Round is over
					isRoundOver = true;
				}
			}
		}

	}
	
	/**
	 * Modifier Method - Sends word to drawer, word type and hints to guessers.
	 */
	public void sendWordSet()
	{	
		// Get word set, using a random number, which is uniformly distributed
		word 	 = wordList.getWord();
		wordType = wordList.getWordType();
		hint1    = wordList.getHint1();
		hint2    = wordList.getHint2();
		
		hint = new ArrayList();
		hint.add(hint1);
		hint.add(hint2);
		
		for (int i = 0; i < clientArray.length; i++)
		{
			playerGameInfo = clientArray[i].getPlayerGameInfo();
			
			sourceSeqNumber++;
			
			// Player is guesser
			if(playerGameInfo.getPlayerMode())
			{
				dataMessage = new DataMessage(sourceSeqNumber,0,null,null,hint,null,0,0,wordType);
				
				// send message
			}
			// Player is drawer
			else
			{
				dataMessage = new DataMessage(sourceSeqNumber,0,null,null,null,word,0,0,null);
				
				// send message
			}
		}
	}
	
	/**
	 * Modifier Method - Sets player's score.
	 */
	public void setScore()
	{
		for (int i = 0; i < clientArray.length; i++)
		{
			playerGameInfo = clientArray[i].getPlayerGameInfo();
			
			// Player has correct guess
			if(playerGameInfo.getGuessTime() != 0)
			{
				
				roundScore = (int)(ROUND_TIME * 10 / playerGameInfo.getGuessTime());
				
				switch(playerGameInfo.getHintsUsed())
				{
					case 0:
					{
						playerGameInfo.setRoundScore(roundScore);
						break;
					}
					case 1:
					{
						roundScore = (int)(0.5 * roundScore);
					
						playerGameInfo.setRoundScore(roundScore);
						break;
					}
					case 2:
					{
						roundScore = (int)(0.25 * roundScore);
					
						playerGameInfo.setRoundScore(roundScore);
						break;
					}
					default:
					{
						roundScore = 0;
						
						playerGameInfo.setRoundScore(roundScore);
						break;
					}
				}
				
				// Update total score
				playerGameInfo.setTotalScore(playerGameInfo.getTotalScore() + roundScore);
			}
		}
		
		// Set maximum round score
		playerGameInfo = clientArray[maxScoreIndex].getPlayerGameInfo();	
		maxScore = playerGameInfo.getRoundScore();
		
		// Check client array for the maximum round score
		for (int i = 1; i < clientArray.length; i++)
		{
			playerGameInfo = clientArray[i].getPlayerGameInfo();
			
			// Player has higher score than current maximum round score
			if(maxScore < playerGameInfo.getRoundScore())
			{
				maxScore = playerGameInfo.getRoundScore();
				maxScoreIndex = i;
			}
		}
		
		// Set drawer score
		for (int i = 0; i < clientArray.length; i++)
		{
			// Check if player is drawer
			if(!playerGameInfo.getPlayerMode())
			{
				roundScore = (int)(0.5 * maxScore);
				
				playerGameInfo.setRoundScore(roundScore);
				
				// Update total score
				playerGameInfo.setTotalScore(playerGameInfo.getTotalScore() + roundScore);
			}
			
			// Update rounds played
			playerGameInfo.setRoundCount(playerGameInfo.getRoundCount() + 1);
		}
	}
	
	/**
	 * Modifier Method - Sets player's mode.
	 */
	public void setPlayerMode()
	{	
		for (int i = 0; i < clientArray.length; i++)
		{
			playerGameInfo = clientArray[i].getPlayerGameInfo();
			
			// Player with highest score
			if(i == maxScoreIndex)
			{
				// Set player mode to drawer
				playerGameInfo.setPlayerMode(false);
			}
			else
			{
				// Set player mode to guesser
				playerGameInfo.setPlayerMode(true);			
			}
		}
	}
	
	/**
	 * Modifier Method - Updates round given a player joins the round.
	 * @param playerInfo	Player game information
	 */
	public void playerJoins(PlayerGameInfo playerInfo)
	{		
		if(roundTime < (int)(0.75 * ROUND_TIME))
		{
			// Update the client array
			clientInfo = Connection.getClientPool();
			clientCollection = clientInfo.values();
			clientArray = (ClientInfoPool [])clientCollection.toArray();
			
			// Set player to guesser
			playerInfo.setPlayerMode(true);
			
			// Send drawing on screen method
		}
		else
		{
			// Player must wait until round is over
		}
	}
	
	/**
	 * Modifier Method - Updates round given a player drops from the round.
	 * @param playerInfo	Player game information
	 */
	public void playerDrops(PlayerGameInfo playerInfo)
	{
		// Update the client array
		clientInfo = Connection.getClientPool();
		clientCollection = clientInfo.values();
		clientArray = (ClientInfoPool [])clientCollection.toArray();
		
		// Player is guesser
		if(playerInfo.getPlayerMode())
		{
			// Check that there are at least 3 players in round
			if(clientArray.length < 3)
			{
				// Round can continue
			}
			else
			{
				isRoundOver = true;
			}
		}
		// Player is drawer
		else
		{
			isRoundOver = true;
		}
	}
	
	/**
	 * Special Method - Waits for a given amount of seconds. 	
	 * @param	sec		number of seconds to wait
	 */
	public void wait(int sec)
	{
		try
		{
			// Do nothing for sec seconds
			Thread.sleep(sec * 1000); 
		}
		catch(InterruptedException e)
		{
			e.printStackTrace();
		}
	}

}
